package lol.clann.bukkit.pluginboot.util;

import lol.clann.clannboot.annotation.inject.Inject;
import lol.clann.clannboot.annotation.svc.PostInstance;
import lol.clann.clannboot.annotation.svc.Service;
import lol.clann.clannboot.util.clazz.*;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Predicate;

/**
 * @author pyz
 * @date 2018/8/6 下午10:42
 */
@Service(priority = Integer.MAX_VALUE)
public class InventoryUtils {

    public static final WrapClass classCraftInventory = ClassUtils.forName("{cb}.inventory.CraftInventory");
    public static final WrapClass classNMSIInventory = ClassUtils.forName("{nms}.IInventory");
    public static final WrapClass classNMSPlayerInventory = ClassUtils.forName("{nms}.PlayerInventory");
    public static final WrapClass classNMSInventoryEnderChest = ClassUtils.forName("{nms}.InventoryEnderChest");
    @Inject
    private ItemStackUtils itemUtils;
    @Inject
    private PlayerUtils playerUtils;

    private WrapField fieldInventory;
    //nms
    private WrapMethod<Integer> getSizeInventory;
    private WrapMethod setInventorySlotContents;
    private WrapMethod markDirty;
    private WrapMethod closeInventory;

    @PostInstance
    private void init() {
        fieldInventory = classCraftInventory.getField("inventory");
        //nms
        getSizeInventory = classNMSIInventory.getMethod(MethodFilter.build().name("getSize", "getSizeInventory"));
        setInventorySlotContents = classNMSIInventory.getMethod(MethodFilter.build().returnVoid().parameterType(int.class, ItemStackUtils.classNMSItemStack.getHandle()));
        markDirty = classNMSIInventory.getMethod(MethodFilter.build().returnVoid().name("markDirty", "update"));
        closeInventory = classNMSIInventory.getMethod(MethodFilter.build().returnVoid().name("closeChest", "closeContainer"));
    }

    public boolean isNMSInventory(Object nms) {
        return classNMSIInventory.isInstance(nms);
    }

    public void safeClose(Inventory inventory) {
        closeInventory.invoke(toNMS(inventory));
    }

    public void markDirty(Inventory inventory) {
        markDirty.invoke(toNMS(inventory));
    }

    public Object toNMS(Inventory inventory) {
        return this.fieldInventory.get(inventory);
    }

    public List<Integer> getInventoryEmpySlot(Inventory inventory) {
        List<Integer> empty = new ArrayList<>();
        int n = 0;
        for (int i = 0; i < inventory.getSize(); i++) {
            if (itemUtils.isEmpty(inventory.getItem(i))) {
                empty.add(i);
            }
        }
        return empty;
    }

    /**
     * 从背包中取走指定数量的物品
     * 只有背包中物品数量足,且取走了大于0个物品,才返回true
     *
     * @param inv
     * @param itemStack
     * @param count
     * @return false表示背包未变动
     */
    public boolean take(Inventory inv, ItemStack itemStack, int count) {
        if (count <= 0) {
            return false;
        }
        Map<Integer, ItemStack> items = getInventoryContent(inv, is -> itemUtils.isSimilar(is, itemStack));
        int totalCount = items.values().stream().mapToInt(ItemStack::getAmount).sum();
        if (totalCount < count) {
            return false;
        }
        for (Map.Entry<Integer, ItemStack> en : items.entrySet()) {
            if (count == 0) {
                break;
            }
            if (count >= en.getValue().getAmount()) {
                inv.setItem(en.getKey(), null);
                count -= en.getValue().getAmount();
            } else {
                en.getValue().setAmount(en.getValue().getAmount() - count);
                inv.setItem(en.getKey(), en.getValue());
                count = 0;
            }
        }
        return true;
    }

    /**
     * 将指定物品放入背包
     * 只有背包空间足够容纳所有物品,且物品全部放入才会返回true
     *
     * @param inv
     * @param items 要求每个元素物品不为null,且数量都不超过该物品的叠堆数,否则可能抛出异常,
     * @return false表示背包未变动
     */
    public boolean put(@Nonnull Inventory inv, @Nonnull List<ItemStack> items) {
        if (items.size() == 0) {
            return false;
        }
        List<Integer> empty = getInventoryEmpySlot(inv);
        if (empty.size() < items.size()) {
            return false;
        }
        Iterator<ItemStack> itItem = items.iterator();
        Iterator<Integer> itSlot = empty.iterator();
        while (itItem.hasNext()) {
            ItemStack item = itItem.next();
            inv.setItem(itSlot.next(), item);
        }
        return true;
    }

    /**
     * 放入指定数量的物品到背包(会自动叠堆)
     * 只有全部放入才会返回true,其余情况背包未变动
     *
     * @param inv
     * @param item
     * @param count
     * @return
     */
    public boolean put(@Nonnull Inventory inv, @Nonnull ItemStack item, int count) {
        List<ItemStack> items = itemUtils.organise(item, count);
        return put(inv, items);
    }

    public void setInventorySlotContents(Object nmsInventory, int index, ItemStack itemStack) {
        setInventorySlotContents.invoke(nmsInventory, index, itemUtils.toNMS(itemStack));
    }

    public int getSizeInventory(Object nmsInventory) {
        return getSizeInventory.invoke(nmsInventory);
    }

    /**
     * 返回第一个可叠堆该物品的格子
     *
     * @param inv
     * @param is
     */
    public int firstPartial(Inventory inv, ItemStack is) {
        if (inv != null || is != null) {
            return -1;
        }
        ItemStack filteredItem = itemUtils.copy(is);
        if (filteredItem == null) {
            return -1;
        }
        ItemStack[] inventory = inv.getContents();
        for (int i = 0; i < inventory.length; i++) {
            ItemStack cItem = inventory[i];
            if ((cItem != null) && (cItem.getAmount() < cItem.getMaxStackSize()) && (cItem.isSimilar(filteredItem))) {
                return i;
            }
        }
        return -1;
    }

    public Map<Integer, ItemStack> getInventoryContent(Inventory inventory) {
        return getInventoryContent(inventory, (Predicate<ItemStack>) null);
    }

    public Map<Integer, ItemStack> getInventoryContent(Inventory inventory, ItemStack similar) {
        return getInventoryContent(inventory, itemStack -> itemUtils.isSimilar(similar, itemStack));
    }

    public Map<Integer, ItemStack> getInventoryContent(Inventory inventory, @Nullable Predicate<ItemStack> filter) {
        Map<Integer, ItemStack> map = new HashMap<>();
        for (int i = 0; i < inventory.getSize(); i++) {
            ItemStack item = inventory.getItem(i);
            if (itemUtils.isEmpty(item) || (filter != null && !filter.test(item))) {
                continue;
            }
            map.put(i, item);
        }
        return map;
    }
}
